/* * This file is part of JLine Console Log4j2 Appender, licensed under the MIT License (MIT). * * Copyright (c) 2013 Wolf480pl <wolf480@interia.pl/> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.wolf480pl.jline_log4j2_appender; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import jline.console.ConsoleReader; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.appender.OutputStreamManager; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.message.Message; import org.fusesource.jansi.AnsiConsole; import com.github.wolf480pl.jline_log4j2_appender.HookableOutputStreamManager.Listener; @Plugin(name = "JLineConsole", category = "Core", elementType = "appender", printObject = true) public class JLineConsoleAppender extends AbstractOutputStreamAppender { private final HookableOutputStreamManager manager; private OutputStreamManager held; // Make sure they don't close System.out or System.err when we're still using it. protected JLineConsoleAppender(String name, Layout<? extends Serializable> layout, Filter filter, HookableOutputStreamManager manager, OutputStreamManager held, boolean ignoreExceptions) { super(name, layout, filter, ignoreExceptions, true, manager); this.manager = manager; this.held = held; } @Override public void append(LogEvent event) { Message msg = event.getMessage(); if (msg instanceof ConsoleSetupMessage) { ConsoleReader reader = ((ConsoleSetupMessage) msg).getReader(); Listener listener = new ConsoleReaderListener(reader); switch (((ConsoleSetupMessage) msg).getAction()) { case ADD: this.manager.addListener(listener); break; case REMOVE: this.manager.removeListener(listener); } } super.append(event); } @Override public void stop() { super.stop(); this.held.release(); } @PluginFactory public static JLineConsoleAppender createAppender(@PluginElement("Layout") Layout<? extends Serializable> layout, @PluginElement("Filters") final Filter filter, @PluginAttribute("target") final String t, @PluginAttribute("name") final String name, @PluginAttribute("follow") final String follow, @PluginAttribute("ignoreExceptions") final String ignore) { if (name == null) { LOGGER.error("No name provided for ConsoleAppender"); return null; } if (layout == null) { layout = PatternLayout.newBuilder().build(); } final boolean isFollow = Boolean.parseBoolean(follow); final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); final Target target = t == null ? Target.SYSTEM_OUT : Target.valueOf(t); FactoryData data = new FactoryData(getStream(isFollow, target), layout); return new JLineConsoleAppender(name, layout, filter, getManager(isFollow, target, data), getHeldManager(isFollow, target, data), ignoreExceptions); } protected static HookableOutputStreamManager getManager(boolean follow, Target target, FactoryData data) { return HookableOutputStreamManager.getHookableManager(target.name() + ".jline." + follow, data, FACTORY); } protected static OutputStreamManager getHeldManager(boolean follow, Target target, FactoryData data) { return OutputStreamManager.getManager(target.name() + "." + follow, data, FACTORY); } protected static HookableOutputStreamManager getManager(boolean follow, Target target, Layout<? extends Serializable> layout) { OutputStream stream; stream = getStream(follow, target); return HookableOutputStreamManager.getHookableManager(target.name() + ".jline." + follow, new FactoryData(stream, layout), FACTORY); } protected static OutputStream getStream(boolean follow, Target target) { OutputStream os; if (target == Target.SYSTEM_ERR) { os = follow ? new SystemErrStream() : new NeverClosingOutputStream(System.err); } else { os = follow ? new SystemOutStream() : new NeverClosingOutputStream(System.out); } return AnsiConsole.wrapOutputStream(os); } public static class FactoryData { private final OutputStream os; private final Layout<? extends Serializable> layout; public FactoryData(final OutputStream os, final Layout<? extends Serializable> layout) { this.os = os; this.layout = layout; } } public static ConsoleManagerFactory FACTORY = new ConsoleManagerFactory(); public static class ConsoleManagerFactory implements ManagerFactory<HookableOutputStreamManager, FactoryData> { @Override public HookableOutputStreamManager createManager(String name, FactoryData data) { return new HookableOutputStreamManager(data.os, name, data.layout); } } public static class ConsoleReaderListener implements Listener { private final ConsoleReader reader; private boolean writing = false; public ConsoleReaderListener(ConsoleReader reader) { this.reader = reader; } @Override public void onWrite() { if (!this.writing) { this.writing = true; try { this.reader.print(String.valueOf(ConsoleReader.RESET_LINE)); this.reader.flush(); } catch (IOException e) { LOGGER.error(e); } } } @Override public void onFlush() { this.writing = false; try { this.reader.drawLine(); } catch (IOException e) { this.reader.getCursorBuffer().clear(); LOGGER.error(e); } try { this.reader.flush(); } catch (IOException e) { LOGGER.error(e); } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof ConsoleReaderListener) { return this.reader == ((ConsoleReaderListener) obj).reader; } return false; } @Override public int hashCode() { return this.reader.hashCode(); } } }